Projeto: Uma Jornada pelos Dados da Olist¶
A Análise¶
A compreensão do cenário do comércio eletrônico brasileiro é essencial para identificar tendências e oportunidades no mercado. Este projeto se concentra na análise do conjunto de dados da Olist, uma plataforma de comércio eletrônico que desempenha um papel crucial ao conectar pequenas empresas a canais de vendas online, facilitando a comercialização de produtos em diversos marketplaces no Brasil. Além disso, a Olist oferece serviços de logística e gerenciamento de pedidos para seus clientes.
Objeto de Estudo¶
O foco da análise recai sobre um conjunto de dados abrangente, composto por informações de 100 mil pedidos realizados na loja Olist no período de 2016 a 2018. Esses pedidos foram efetuados em vários marketplaces brasileiros, proporcionando uma visão detalhada das transações comerciais. A análise contempla diversas dimensões, incluindo: Características das Vendas e Receitas, Horários das Vendas, Avaliações dos Clientes, Logística e Entrega.
Essa abordagem multifacetada visa não apenas avaliar o desempenho da plataforma, mas também fornecer insights profundos sobre o funcionamento da Olist. A análise dos dados reais e anonimizados contribui para a compreensão das dinâmicas do mercado, facilitando a identificação de oportunidades de aprimoramento e tomada de decisões estratégicas no setor.
Tópicos¶
- Impotando Bibliotecas
- Importando os dados
- "Cheirando" os Dados
- Renomeando Colunas
- Juntando os DataFrames
- Identificando valores nulos
- Estatística Descritiva
- Distribuídas as avaliações (notas)
- Análise de Pareto
- Clientes que mais gastam
- Principais cidades por número de pedidos por estado
- Cidades com maior geração de receita
- Pareto das Cidades
- Variação ao longo do tempo
- Avaliação dos Produtos
- O método de pagamento afeta o status do pedido?
- Há alguma relação entre o tempo de entrega e as pontuações das avaliações?
- Quais são as cidades dos vendedores com menor/maior tempo de entrega
- Estados com maior/menor tempo de entrega
- Como o tempo médio de entrega varia ao longo do tempo?
- Como a pontuação média das avaliações varia ao longo do tempo
- Categorias de produtos mais vendidas
- Mapa coroplético com o tempo de entrega em dias por cada unidade federativa
- Considerações sobre a Análise
- Referências
Importando Bibliotecas¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
import datetime
import warnings
warnings.filterwarnings("ignore")
customers_df = pd.read_csv("./data/raw/olist_customers_dataset.csv")
geo_df = pd.read_csv("./data/raw/olist_geolocation_dataset.csv")
orderitem_df = pd.read_csv("./data/raw/olist_order_items_dataset.csv")
orderpay_df = pd.read_csv("./data/raw/olist_order_payments_dataset.csv")
orderreviews_df = pd.read_csv("./data/raw/olist_order_reviews_dataset.csv")
orders_df = pd.read_csv("./data/raw/olist_orders_dataset.csv")
products_df = pd.read_csv("./data/raw/olist_products_dataset.csv")
sellers_df = pd.read_csv("./data/raw/olist_sellers_dataset.csv")
categname_df = pd.read_csv("./data/raw/product_category_name_translation.csv")
pd.set_option('display.max_columns', 500)
# Vendo as 10 linhas do arquivo de clientes
customers_df.head(10)
| customer_id | customer_unique_id | customer_zip_code_prefix | customer_city | customer_state | |
|---|---|---|---|---|---|
| 0 | 06b8999e2fba1a1fbc88172c00ba8bc7 | 861eff4711a542e4b93843c6dd7febb0 | 14409 | franca | SP |
| 1 | 18955e83d337fd6b2def6b18a428ac77 | 290c77bc529b7ac935b93aa66c333dc3 | 9790 | sao bernardo do campo | SP |
| 2 | 4e7b3e00288586ebd08712fdd0374a03 | 060e732b5b29e8181a18229c7b0b2b5e | 1151 | sao paulo | SP |
| 3 | b2b6027bc5c5109e529d4dc6358b12c3 | 259dac757896d24d7702b9acbbff3f3c | 8775 | mogi das cruzes | SP |
| 4 | 4f2d8ab171c80ec8364f7c12e35b23ad | 345ecd01c38d18a9036ed96c73b8d066 | 13056 | campinas | SP |
| 5 | 879864dab9bc3047522c92c82e1212b8 | 4c93744516667ad3b8f1fb645a3116a4 | 89254 | jaragua do sul | SC |
| 6 | fd826e7cf63160e536e0908c76c3f441 | addec96d2e059c80c30fe6871d30d177 | 4534 | sao paulo | SP |
| 7 | 5e274e7a0c3809e14aba7ad5aae0d407 | 57b2a98a409812fe9618067b6b8ebe4f | 35182 | timoteo | MG |
| 8 | 5adf08e34b2e993982a47070956c5c65 | 1175e95fb47ddff9de6b2b06188f7e0d | 81560 | curitiba | PR |
| 9 | 4b7139f34592b3a31687243a302fa75b | 9afe194fb833f79e300e37e580171f22 | 30575 | belo horizonte | MG |
# Quantas linhas e colunas tem o dataframe de clientes
customers_df.shape
(99441, 5)
"Cheirando" os Dados¶
Além de conhecer os metadados, temos também que dar uma "cheiradinha" neles! Visualizar algumas linhas, avaliar os tipos de colunas e como as diferentes bases se relacionam.
print("customers_df:")
display(customers_df.head())
print("\ngeo_df:")
display(geo_df.head())
print("\norderitem_df:")
display(orderitem_df.head())
print("\norderpay_df:")
display(orderpay_df.head())
print("\norderreviews_df:")
display(orderreviews_df.head())
print("\norders_df:")
display(orders_df.head())
print("\nproducts_df:")
display(products_df.head())
print("\nsellers_df:")
display(sellers_df.head())
print("\ncategname_df:")
display(categname_df.head())
customers_df:
| customer_id | customer_unique_id | customer_zip_code_prefix | customer_city | customer_state | |
|---|---|---|---|---|---|
| 0 | 06b8999e2fba1a1fbc88172c00ba8bc7 | 861eff4711a542e4b93843c6dd7febb0 | 14409 | franca | SP |
| 1 | 18955e83d337fd6b2def6b18a428ac77 | 290c77bc529b7ac935b93aa66c333dc3 | 9790 | sao bernardo do campo | SP |
| 2 | 4e7b3e00288586ebd08712fdd0374a03 | 060e732b5b29e8181a18229c7b0b2b5e | 1151 | sao paulo | SP |
| 3 | b2b6027bc5c5109e529d4dc6358b12c3 | 259dac757896d24d7702b9acbbff3f3c | 8775 | mogi das cruzes | SP |
| 4 | 4f2d8ab171c80ec8364f7c12e35b23ad | 345ecd01c38d18a9036ed96c73b8d066 | 13056 | campinas | SP |
geo_df:
| geolocation_zip_code_prefix | geolocation_lat | geolocation_lng | geolocation_city | geolocation_state | |
|---|---|---|---|---|---|
| 0 | 1037 | -23.545621 | -46.639292 | sao paulo | SP |
| 1 | 1046 | -23.546081 | -46.644820 | sao paulo | SP |
| 2 | 1046 | -23.546129 | -46.642951 | sao paulo | SP |
| 3 | 1041 | -23.544392 | -46.639499 | sao paulo | SP |
| 4 | 1035 | -23.541578 | -46.641607 | sao paulo | SP |
orderitem_df:
| order_id | order_item_id | product_id | seller_id | shipping_limit_date | price | freight_value | |
|---|---|---|---|---|---|---|---|
| 0 | 00010242fe8c5a6d1ba2dd792cb16214 | 1 | 4244733e06e7ecb4970a6e2683c13e61 | 48436dade18ac8b2bce089ec2a041202 | 2017-09-19 09:45:35 | 58.90 | 13.29 |
| 1 | 00018f77f2f0320c557190d7a144bdd3 | 1 | e5f2d52b802189ee658865ca93d83a8f | dd7ddc04e1b6c2c614352b383efe2d36 | 2017-05-03 11:05:13 | 239.90 | 19.93 |
| 2 | 000229ec398224ef6ca0657da4fc703e | 1 | c777355d18b72b67abbeef9df44fd0fd | 5b51032eddd242adc84c38acab88f23d | 2018-01-18 14:48:30 | 199.00 | 17.87 |
| 3 | 00024acbcdf0a6daa1e931b038114c75 | 1 | 7634da152a4610f1595efa32f14722fc | 9d7a1d34a5052409006425275ba1c2b4 | 2018-08-15 10:10:18 | 12.99 | 12.79 |
| 4 | 00042b26cf59d7ce69dfabb4e55b4fd9 | 1 | ac6c3623068f30de03045865e4e10089 | df560393f3a51e74553ab94004ba5c87 | 2017-02-13 13:57:51 | 199.90 | 18.14 |
orderpay_df:
| order_id | payment_sequential | payment_type | payment_installments | payment_value | |
|---|---|---|---|---|---|
| 0 | b81ef226f3fe1789b1e8b2acac839d17 | 1 | credit_card | 8 | 99.33 |
| 1 | a9810da82917af2d9aefd1278f1dcfa0 | 1 | credit_card | 1 | 24.39 |
| 2 | 25e8ea4e93396b6fa0d3dd708e76c1bd | 1 | credit_card | 1 | 65.71 |
| 3 | ba78997921bbcdc1373bb41e913ab953 | 1 | credit_card | 8 | 107.78 |
| 4 | 42fdf880ba16b47b59251dd489d4441a | 1 | credit_card | 2 | 128.45 |
orderreviews_df:
| review_id | order_id | review_score | review_comment_title | review_comment_message | review_creation_date | review_answer_timestamp | |
|---|---|---|---|---|---|---|---|
| 0 | 7bc2406110b926393aa56f80a40eba40 | 73fc7af87114b39712e6da79b0a377eb | 4 | NaN | NaN | 2018-01-18 00:00:00 | 2018-01-18 21:46:59 |
| 1 | 80e641a11e56f04c1ad469d5645fdfde | a548910a1c6147796b98fdf73dbeba33 | 5 | NaN | NaN | 2018-03-10 00:00:00 | 2018-03-11 03:05:13 |
| 2 | 228ce5500dc1d8e020d8d1322874b6f0 | f9e4b658b201a9f2ecdecbb34bed034b | 5 | NaN | NaN | 2018-02-17 00:00:00 | 2018-02-18 14:36:24 |
| 3 | e64fb393e7b32834bb789ff8bb30750e | 658677c97b385a9be170737859d3511b | 5 | NaN | Recebi bem antes do prazo estipulado. | 2017-04-21 00:00:00 | 2017-04-21 22:02:06 |
| 4 | f7c4243c7fe1938f181bec41a392bdeb | 8e6bfb81e283fa7e4f11123a3fb894f1 | 5 | NaN | Parabéns lojas lannister adorei comprar pela I... | 2018-03-01 00:00:00 | 2018-03-02 10:26:53 |
orders_df:
| order_id | customer_id | order_status | order_purchase_timestamp | order_approved_at | order_delivered_carrier_date | order_delivered_customer_date | order_estimated_delivery_date | |
|---|---|---|---|---|---|---|---|---|
| 0 | e481f51cbdc54678b7cc49136f2d6af7 | 9ef432eb6251297304e76186b10a928d | delivered | 2017-10-02 10:56:33 | 2017-10-02 11:07:15 | 2017-10-04 19:55:00 | 2017-10-10 21:25:13 | 2017-10-18 00:00:00 |
| 1 | 53cdb2fc8bc7dce0b6741e2150273451 | b0830fb4747a6c6d20dea0b8c802d7ef | delivered | 2018-07-24 20:41:37 | 2018-07-26 03:24:27 | 2018-07-26 14:31:00 | 2018-08-07 15:27:45 | 2018-08-13 00:00:00 |
| 2 | 47770eb9100c2d0c44946d9cf07ec65d | 41ce2a54c0b03bf3443c3d931a367089 | delivered | 2018-08-08 08:38:49 | 2018-08-08 08:55:23 | 2018-08-08 13:50:00 | 2018-08-17 18:06:29 | 2018-09-04 00:00:00 |
| 3 | 949d5b44dbf5de918fe9c16f97b45f8a | f88197465ea7920adcdbec7375364d82 | delivered | 2017-11-18 19:28:06 | 2017-11-18 19:45:59 | 2017-11-22 13:39:59 | 2017-12-02 00:28:42 | 2017-12-15 00:00:00 |
| 4 | ad21c59c0840e6cb83a9ceb5573f8159 | 8ab97904e6daea8866dbdbc4fb7aad2c | delivered | 2018-02-13 21:18:39 | 2018-02-13 22:20:29 | 2018-02-14 19:46:34 | 2018-02-16 18:17:02 | 2018-02-26 00:00:00 |
products_df:
| product_id | product_category_name | product_name_lenght | product_description_lenght | product_photos_qty | product_weight_g | product_length_cm | product_height_cm | product_width_cm | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1e9e8ef04dbcff4541ed26657ea517e5 | perfumaria | 40.0 | 287.0 | 1.0 | 225.0 | 16.0 | 10.0 | 14.0 |
| 1 | 3aa071139cb16b67ca9e5dea641aaa2f | artes | 44.0 | 276.0 | 1.0 | 1000.0 | 30.0 | 18.0 | 20.0 |
| 2 | 96bd76ec8810374ed1b65e291975717f | esporte_lazer | 46.0 | 250.0 | 1.0 | 154.0 | 18.0 | 9.0 | 15.0 |
| 3 | cef67bcfe19066a932b7673e239eb23d | bebes | 27.0 | 261.0 | 1.0 | 371.0 | 26.0 | 4.0 | 26.0 |
| 4 | 9dc1a7de274444849c219cff195d0b71 | utilidades_domesticas | 37.0 | 402.0 | 4.0 | 625.0 | 20.0 | 17.0 | 13.0 |
sellers_df:
| seller_id | seller_zip_code_prefix | seller_city | seller_state | |
|---|---|---|---|---|
| 0 | 3442f8959a84dea7ee197c632cb2df15 | 13023 | campinas | SP |
| 1 | d1b65fc7debc3361ea86b5f14c68d2e2 | 13844 | mogi guacu | SP |
| 2 | ce3ad9de960102d0677a81f5d0bb7b2d | 20031 | rio de janeiro | RJ |
| 3 | c0f3eea2e14555b6faeea3dd58c1b1c3 | 4195 | sao paulo | SP |
| 4 | 51a04a8a6bdcb23deccc82b0b80742cf | 12914 | braganca paulista | SP |
categname_df:
| product_category_name | product_category_name_english | |
|---|---|---|
| 0 | beleza_saude | health_beauty |
| 1 | informatica_acessorios | computers_accessories |
| 2 | automotivo | auto |
| 3 | cama_mesa_banho | bed_bath_table |
| 4 | moveis_decoracao | furniture_decor |
print("customers_df:\n")
display(customers_df.info())
print("\ngeo_df:\n")
display(geo_df.info())
print("\norderitem_df:\n")
display(orderitem_df.info())
print("\norderpay_df:\n")
display(orderpay_df.info())
print("\norderreviews_df:\n")
display(orderreviews_df.info())
print("\norders_df:\n")
display(orders_df.info())
print("\nproducts_df:\n")
display(products_df.info())
print("\nsellers_df:\n")
display(sellers_df.info())
print("\ncategname_df:\n")
display(categname_df.info())
customers_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 99441 entries, 0 to 99440 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customer_id 99441 non-null object 1 customer_unique_id 99441 non-null object 2 customer_zip_code_prefix 99441 non-null int64 3 customer_city 99441 non-null object 4 customer_state 99441 non-null object dtypes: int64(1), object(4) memory usage: 3.8+ MB
None
geo_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 1000163 entries, 0 to 1000162 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 geolocation_zip_code_prefix 1000163 non-null int64 1 geolocation_lat 1000163 non-null float64 2 geolocation_lng 1000163 non-null float64 3 geolocation_city 1000163 non-null object 4 geolocation_state 1000163 non-null object dtypes: float64(2), int64(1), object(2) memory usage: 38.2+ MB
None
orderitem_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 112650 entries, 0 to 112649 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 order_id 112650 non-null object 1 order_item_id 112650 non-null int64 2 product_id 112650 non-null object 3 seller_id 112650 non-null object 4 shipping_limit_date 112650 non-null object 5 price 112650 non-null float64 6 freight_value 112650 non-null float64 dtypes: float64(2), int64(1), object(4) memory usage: 6.0+ MB
None
orderpay_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 103886 entries, 0 to 103885 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 order_id 103886 non-null object 1 payment_sequential 103886 non-null int64 2 payment_type 103886 non-null object 3 payment_installments 103886 non-null int64 4 payment_value 103886 non-null float64 dtypes: float64(1), int64(2), object(2) memory usage: 4.0+ MB
None
orderreviews_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 99224 entries, 0 to 99223 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 review_id 99224 non-null object 1 order_id 99224 non-null object 2 review_score 99224 non-null int64 3 review_comment_title 11568 non-null object 4 review_comment_message 40977 non-null object 5 review_creation_date 99224 non-null object 6 review_answer_timestamp 99224 non-null object dtypes: int64(1), object(6) memory usage: 5.3+ MB
None
orders_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 99441 entries, 0 to 99440 Data columns (total 8 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 order_id 99441 non-null object 1 customer_id 99441 non-null object 2 order_status 99441 non-null object 3 order_purchase_timestamp 99441 non-null object 4 order_approved_at 99281 non-null object 5 order_delivered_carrier_date 97658 non-null object 6 order_delivered_customer_date 96476 non-null object 7 order_estimated_delivery_date 99441 non-null object dtypes: object(8) memory usage: 6.1+ MB
None
products_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 32951 entries, 0 to 32950 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 product_id 32951 non-null object 1 product_category_name 32341 non-null object 2 product_name_lenght 32341 non-null float64 3 product_description_lenght 32341 non-null float64 4 product_photos_qty 32341 non-null float64 5 product_weight_g 32949 non-null float64 6 product_length_cm 32949 non-null float64 7 product_height_cm 32949 non-null float64 8 product_width_cm 32949 non-null float64 dtypes: float64(7), object(2) memory usage: 2.3+ MB
None
sellers_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 3095 entries, 0 to 3094 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 seller_id 3095 non-null object 1 seller_zip_code_prefix 3095 non-null int64 2 seller_city 3095 non-null object 3 seller_state 3095 non-null object dtypes: int64(1), object(3) memory usage: 96.8+ KB
None
categname_df: <class 'pandas.core.frame.DataFrame'> RangeIndex: 71 entries, 0 to 70 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 product_category_name 71 non-null object 1 product_category_name_english 71 non-null object dtypes: object(2) memory usage: 1.2+ KB
None
Renomeando Colunas¶
Neste trecho de código iremos renomear a coluna customer_zip_code_prefix do DataFrame customers_df para zip_code e a coluna geolocation_zip_code_prefix do DataFrame geo_df para zip_code, usando o método rename() da biblioteca pandas.
Renomear colunas pode ser útil em várias situações, como quando as colunas têm nomes longos e complicados, nomes com caracteres especiais ou para tornar os nomes das colunas mais descritivos e padronizados. Nesse caso, é somente para facilitar juntarmos os dados das bases de clientes e de geolocalizacao (por meio do CEP)
customers_df = customers_df.rename(columns={"customer_zip_code_prefix": "zip_code"})
geo_df = geo_df.rename(columns={"geolocation_zip_code_prefix": "zip_code"})
customers_df.head()
| customer_id | customer_unique_id | zip_code | customer_city | customer_state | |
|---|---|---|---|---|---|
| 0 | 06b8999e2fba1a1fbc88172c00ba8bc7 | 861eff4711a542e4b93843c6dd7febb0 | 14409 | franca | SP |
| 1 | 18955e83d337fd6b2def6b18a428ac77 | 290c77bc529b7ac935b93aa66c333dc3 | 9790 | sao bernardo do campo | SP |
| 2 | 4e7b3e00288586ebd08712fdd0374a03 | 060e732b5b29e8181a18229c7b0b2b5e | 1151 | sao paulo | SP |
| 3 | b2b6027bc5c5109e529d4dc6358b12c3 | 259dac757896d24d7702b9acbbff3f3c | 8775 | mogi das cruzes | SP |
| 4 | 4f2d8ab171c80ec8364f7c12e35b23ad | 345ecd01c38d18a9036ed96c73b8d066 | 13056 | campinas | SP |
Juntando os DataFrames¶
Este trecho de código combina os DataFrames orders_df, customers_df, orderitem_df, products_df, categname_df, orderpay_df, sellers_df e orderreviews_df, usando o método merge() da biblioteca pandas. Cada DataFrame é combinado com o próximo usando uma coluna em comum como chave de junção.
Esse processo de combinar vários DataFrames em um só é útil para consolidar dados dispersos em um único lugar e facilitar a análise dos dados.
data = orders_df.merge(customers_df, on="customer_id").merge(orderitem_df, on="order_id").merge(products_df, on="product_id").merge(categname_df, on="product_category_name").merge(orderpay_df, on="order_id").merge(sellers_df, on="seller_id").merge(orderreviews_df, on="order_id")
data.head()
| order_id | customer_id | order_status | order_purchase_timestamp | order_approved_at | order_delivered_carrier_date | order_delivered_customer_date | order_estimated_delivery_date | customer_unique_id | zip_code | customer_city | customer_state | order_item_id | product_id | seller_id | shipping_limit_date | price | freight_value | product_category_name | product_name_lenght | product_description_lenght | product_photos_qty | product_weight_g | product_length_cm | product_height_cm | product_width_cm | product_category_name_english | payment_sequential | payment_type | payment_installments | payment_value | seller_zip_code_prefix | seller_city | seller_state | review_id | review_score | review_comment_title | review_comment_message | review_creation_date | review_answer_timestamp | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | e481f51cbdc54678b7cc49136f2d6af7 | 9ef432eb6251297304e76186b10a928d | delivered | 2017-10-02 10:56:33 | 2017-10-02 11:07:15 | 2017-10-04 19:55:00 | 2017-10-10 21:25:13 | 2017-10-18 00:00:00 | 7c396fd4830fd04220f754e42b4e5bff | 3149 | sao paulo | SP | 1 | 87285b34884572647811a353c7ac498a | 3504c0cb71d7fa48d967e0e4c94d59d9 | 2017-10-06 11:07:15 | 29.99 | 8.72 | utilidades_domesticas | 40.0 | 268.0 | 4.0 | 500.0 | 19.0 | 8.0 | 13.0 | housewares | 1 | credit_card | 1 | 18.12 | 9350 | maua | SP | a54f0611adc9ed256b57ede6b6eb5114 | 4 | NaN | Não testei o produto ainda, mas ele veio corre... | 2017-10-11 00:00:00 | 2017-10-12 03:43:48 |
| 1 | e481f51cbdc54678b7cc49136f2d6af7 | 9ef432eb6251297304e76186b10a928d | delivered | 2017-10-02 10:56:33 | 2017-10-02 11:07:15 | 2017-10-04 19:55:00 | 2017-10-10 21:25:13 | 2017-10-18 00:00:00 | 7c396fd4830fd04220f754e42b4e5bff | 3149 | sao paulo | SP | 1 | 87285b34884572647811a353c7ac498a | 3504c0cb71d7fa48d967e0e4c94d59d9 | 2017-10-06 11:07:15 | 29.99 | 8.72 | utilidades_domesticas | 40.0 | 268.0 | 4.0 | 500.0 | 19.0 | 8.0 | 13.0 | housewares | 3 | voucher | 1 | 2.00 | 9350 | maua | SP | a54f0611adc9ed256b57ede6b6eb5114 | 4 | NaN | Não testei o produto ainda, mas ele veio corre... | 2017-10-11 00:00:00 | 2017-10-12 03:43:48 |
| 2 | e481f51cbdc54678b7cc49136f2d6af7 | 9ef432eb6251297304e76186b10a928d | delivered | 2017-10-02 10:56:33 | 2017-10-02 11:07:15 | 2017-10-04 19:55:00 | 2017-10-10 21:25:13 | 2017-10-18 00:00:00 | 7c396fd4830fd04220f754e42b4e5bff | 3149 | sao paulo | SP | 1 | 87285b34884572647811a353c7ac498a | 3504c0cb71d7fa48d967e0e4c94d59d9 | 2017-10-06 11:07:15 | 29.99 | 8.72 | utilidades_domesticas | 40.0 | 268.0 | 4.0 | 500.0 | 19.0 | 8.0 | 13.0 | housewares | 2 | voucher | 1 | 18.59 | 9350 | maua | SP | a54f0611adc9ed256b57ede6b6eb5114 | 4 | NaN | Não testei o produto ainda, mas ele veio corre... | 2017-10-11 00:00:00 | 2017-10-12 03:43:48 |
| 3 | 128e10d95713541c87cd1a2e48201934 | a20e8105f23924cd00833fd87daa0831 | delivered | 2017-08-15 18:29:31 | 2017-08-15 20:05:16 | 2017-08-17 15:28:33 | 2017-08-18 14:44:43 | 2017-08-28 00:00:00 | 3a51803cc0d012c3b5dc8b7528cb05f7 | 3366 | sao paulo | SP | 1 | 87285b34884572647811a353c7ac498a | 3504c0cb71d7fa48d967e0e4c94d59d9 | 2017-08-21 20:05:16 | 29.99 | 7.78 | utilidades_domesticas | 40.0 | 268.0 | 4.0 | 500.0 | 19.0 | 8.0 | 13.0 | housewares | 1 | credit_card | 3 | 37.77 | 9350 | maua | SP | b46f1e34512b0f4c74a72398b03ca788 | 4 | NaN | Deveriam embalar melhor o produto. A caixa vei... | 2017-08-19 00:00:00 | 2017-08-20 15:16:36 |
| 4 | 0e7e841ddf8f8f2de2bad69267ecfbcf | 26c7ac168e1433912a51b924fbd34d34 | delivered | 2017-08-02 18:24:47 | 2017-08-02 18:43:15 | 2017-08-04 17:35:43 | 2017-08-07 18:30:01 | 2017-08-15 00:00:00 | ef0996a1a279c26e7ecbd737be23d235 | 2290 | sao paulo | SP | 1 | 87285b34884572647811a353c7ac498a | 3504c0cb71d7fa48d967e0e4c94d59d9 | 2017-08-08 18:37:31 | 29.99 | 7.78 | utilidades_domesticas | 40.0 | 268.0 | 4.0 | 500.0 | 19.0 | 8.0 | 13.0 | housewares | 1 | credit_card | 1 | 37.77 | 9350 | maua | SP | dc90f19c2806f1abba9e72ad3c350073 | 5 | NaN | Só achei ela pequena pra seis xícaras ,mais é ... | 2017-08-08 00:00:00 | 2017-08-08 23:26:23 |
Temos ids de pedidos duplicados. Isso se deve ao fato de que o mesmo pedido pode ser pago com vários métodos de pagamento diferentes.
Identificando valores nulos¶
# Porcentagem de valores nulos
(100 * data.isna().sum() / len(data) ).sort_values(ascending=False)
review_comment_title 88.062348 review_comment_message 57.697065 order_delivered_customer_date 2.075963 order_delivered_carrier_date 1.033657 order_approved_at 0.012110 product_length_cm 0.000865 product_height_cm 0.000865 product_width_cm 0.000865 product_weight_g 0.000865 payment_installments 0.000000 product_category_name_english 0.000000 payment_sequential 0.000000 payment_type 0.000000 order_id 0.000000 payment_value 0.000000 seller_zip_code_prefix 0.000000 seller_city 0.000000 review_id 0.000000 review_score 0.000000 review_creation_date 0.000000 seller_state 0.000000 product_description_lenght 0.000000 product_photos_qty 0.000000 customer_id 0.000000 order_status 0.000000 order_purchase_timestamp 0.000000 order_estimated_delivery_date 0.000000 customer_unique_id 0.000000 zip_code 0.000000 customer_city 0.000000 customer_state 0.000000 order_item_id 0.000000 product_id 0.000000 seller_id 0.000000 shipping_limit_date 0.000000 price 0.000000 freight_value 0.000000 product_category_name 0.000000 product_name_lenght 0.000000 review_answer_timestamp 0.000000 dtype: float64
Estatística Descritiva¶
data.describe()
| zip_code | order_item_id | price | freight_value | product_name_lenght | product_description_lenght | product_photos_qty | product_weight_g | product_length_cm | product_height_cm | product_width_cm | payment_sequential | payment_installments | payment_value | seller_zip_code_prefix | review_score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115608.000000 | 115608.000000 | 115608.000000 | 115608.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 | 115609.000000 |
| mean | 35061.537597 | 1.194535 | 120.619850 | 20.056880 | 48.766541 | 785.808198 | 2.205373 | 2113.907697 | 30.307903 | 16.638477 | 23.113167 | 1.093747 | 2.946233 | 172.387379 | 24515.713958 | 4.034409 |
| std | 29841.671732 | 0.685926 | 182.653476 | 15.836184 | 10.034187 | 652.418619 | 1.717771 | 3781.754895 | 16.211108 | 13.473570 | 11.755083 | 0.729849 | 2.781087 | 265.873969 | 27636.640968 | 1.385584 |
| min | 1003.000000 | 1.000000 | 0.850000 | 0.000000 | 5.000000 | 4.000000 | 1.000000 | 0.000000 | 7.000000 | 2.000000 | 6.000000 | 1.000000 | 0.000000 | 0.000000 | 1001.000000 | 1.000000 |
| 25% | 11310.000000 | 1.000000 | 39.900000 | 13.080000 | 42.000000 | 346.000000 | 1.000000 | 300.000000 | 18.000000 | 8.000000 | 15.000000 | 1.000000 | 1.000000 | 60.870000 | 6429.000000 | 4.000000 |
| 50% | 24241.000000 | 1.000000 | 74.900000 | 16.320000 | 52.000000 | 600.000000 | 1.000000 | 700.000000 | 25.000000 | 13.000000 | 20.000000 | 1.000000 | 2.000000 | 108.050000 | 13660.000000 | 5.000000 |
| 75% | 58745.000000 | 1.000000 | 134.900000 | 21.210000 | 57.000000 | 983.000000 | 3.000000 | 1800.000000 | 38.000000 | 20.000000 | 30.000000 | 1.000000 | 4.000000 | 189.480000 | 28605.000000 | 5.000000 |
| max | 99980.000000 | 21.000000 | 6735.000000 | 409.680000 | 76.000000 | 3992.000000 | 20.000000 | 40425.000000 | 105.000000 | 105.000000 | 118.000000 | 29.000000 | 24.000000 | 13664.080000 | 99730.000000 | 5.000000 |
Distribuídas as avaliações (notas)¶
# Criando a percentagem
percentage = data["review_score"].value_counts(normalize=True) * 100
#Gráfico
fig = px.bar(percentage, x=percentage.index, y=percentage.values, text=percentage.values, labels={"x": "Nota", "y": "Percentagem"})
fig.update_traces(texttemplate='%{text:.2f}%', textposition='outside')
fig.show()
Mais de 75% dos clientes deram uma pontuação igual ou maior que 4. 12,5% deram uma pontuação de 1 e cerca de 12% deram uma pontuação de 3 ou 2.
Análise de Pareto¶
Análise de Pareto é uma técnica importante para identificar e priorizar os elementos mais relevantes em um conjunto de dados, permitindo a concentração de esforços nas áreas mais críticas e relevantes, maximizando resultados e minimizando custos. É uma ferramenta simples e de fácil aplicação.
É a famosa regra 20/80, 20% que representam 80% do resultado
Podemos utilizar para várias análises! Vamos começar com a proporção de clientes que mais gastam.
# Agrupando os clientes com o maior número cumulativo de pedidos (em pagamentos).
top_customers = data.groupby("customer_unique_id")["payment_value"].sum().reset_index().sort_values("payment_value", ascending=False)
top_customers.rename(columns={"payment_value":"total_paid"}, inplace=True)
# calculaando as colunas "% of Total Sales" e "Cum % of Total Sales"
top_customers["% of Total Sales"] = (top_customers["total_paid"] / top_customers["total_paid"].sum()) * 100
top_customers["Cum % of Total Sales"] = top_customers["% of Total Sales"].cumsum()
# criaando um gráfico de linhas do Plotly
fig = px.line(top_customers, x=range(1, len(top_customers) + 1), y="Cum % of Total Sales")
# definir as etiquetas do eixo x e y e o título do gráfico
fig.update_layout(
xaxis_title="Número de Clientes",
yaxis_title="Total Vendas Cumulativo %",
title="Contribuição % para as vendas por número de clientes"
)
# adicionando uma linha de preenchimento abaixo do gráfico
fig.add_shape(
type="rect",
xref="x",
yref="paper",
x0=0,
y0=0,
x1=40000,
y1=1,
fillcolor="green",
opacity=0.2,
layer="below"
)
# atualizando o layout da forma para ajustar a altura do preenchimento
fig.update_shapes(dict(xref='x', yref='paper'))
# adicionando um texto explicativo na figura
fig.add_annotation(
x=55000,
y=75,
text="40k clientes (+-42% do total)<br> representam +-80% das vendas",
font=dict(
size=14,
color="black"
),
showarrow=False,
)
fig.show()
A partir desta Análise de Pareto, destacamos dois pontos-chave:
Concentração de Receita: Identificamos que cerca de 40% dos clientes correspondem a 80% da receita total. Isso sublinha a importância estratégica de cultivar relacionamentos sólidos com esse grupo seleto de clientes-chave.
Otimização de Recursos: Recursos preciosos, como tempo, esforço e orçamento de marketing, podem ser otimizados ao se concentrar nos clientes de alto valor. Recomendo considerar estratégias específicas, como marketing direcionado, atendimento personalizado e programas de recompensa para maximizar o valor desses clientes.
Riscos e Diversificação: É crucial ponderar os riscos associados à dependência de um pequeno grupo de clientes. Diversificar as fontes de receita pode ser uma estratégia prudente, mitigando potenciais impactos negativos e proporcionando uma base mais estável para o crescimento futuro.
Clientes que mais gastam¶
Os resultados dessa análise podem gerar diversos insights para a área de marketing! Por exemplo:
- Monitorar os clientes que mais gastam-
- Oferecer promoções
- Tentar vender mais produtos
- Realizar entregas personalizadas
# renomeaNDO a coluna "payment_value" para "total_paid"
top_customers.rename(columns={"payment_value" : "total_paid"}, inplace=True)
# criaNDO um gráfico de barras do Plotly
fig = px.bar(top_customers[:10], x="total_paid", y="customer_unique_id", orientation="h")
# atualizando as configurações de layout do gráfico
fig.update_layout(
title="Top 10 Clientes por Valor Total Pago",
xaxis_title="Valor Total Pago",
yaxis_title="ID do Cliente Único"
)
fig.show()
Principais cidades por número de pedidos por estado¶
Vamos verificar quais são as cidades que mais tem números de pedido!
# agrupando o dataframe por "customer_state" e contar o número de "order_id" por estado
top_orders_cities = data.groupby("customer_state")["order_id"].count().reset_index().sort_values("order_id", ascending=False)
# renomeando a coluna "order_id" para "count"
top_orders_cities.rename(columns={"order_id":"count"}, inplace=True)
# criando um gráfico de barras do Plotly
fig = px.bar(top_orders_cities[:10], x="count", y="customer_state", orientation="h")
# atualizando as configurações de layout do gráfico
fig.update_layout(
title="TOP 10 Estados por Número de Pedidos",
xaxis_title="Número de Pedidos",
yaxis_title="Estado"
)
fig.show()
Cidades com maior geração de receita¶
Agora podemos avaliar não a quantidade de pedidos mas o quanto de dinheiro que cada cidade traz
# Fazendo agrupamento e contagem
top_ordersbyvalue_cities = data.groupby("customer_city")["payment_value"].sum().reset_index().sort_values("payment_value", ascending=False)
top_ordersbyvalue_cities["% of Total Payments"] = (top_ordersbyvalue_cities["payment_value"] / top_ordersbyvalue_cities["payment_value"].sum()) * 100
top_ordersbyvalue_cities["Cum % of Total Payments"] = top_ordersbyvalue_cities["% of Total Payments"].cumsum()
# criando um gráfico de barras do Plotly
fig = px.bar(top_ordersbyvalue_cities[:10], x="% of Total Payments", y="customer_city", orientation="h")
# atualizando as configurações de layout do gráfico
fig.update_layout(
title="TOP 10 Cidades por Geração de Receita",
xaxis_title="Porcentagem do Total de Pagamentos",
yaxis_title="Cidade do Cliente"
)
fig.show()
Liderança de São Paulo: O Estado de São Paulo lidera em número de pedidos, indicando uma alta atividade comercial. A capital, São Paulo, contribui com cerca de 14% da receita total, ressaltando a importância econômica dessa região.
Concentração de Receita em São Paulo: A concentração de receita em São Paulo, Campinas e Guarulhos sugere que essas cidades possuem consumidores que realizam compras de maior valor. Isso pode ser explorado através de estratégias específicas de marketing e atendimento ao cliente nessas áreas.
Desempenho Significativo de Brasília: Apesar do Distrito Federal não estar entre os estados com o maior número de vendas, a cidade de Brasília ocupa a quarta posição entre as cidades com maiores receitas. Isso aponta para um comportamento de compra significativo na região, o que pode indicar um mercado de alto valor em Brasília.
Análise de Pareto para Cidades: A análise de Pareto revela que apenas cerca de 9% das cidades representam 80% das vendas. Isso sugere uma concentração notável de atividade econômica em um número limitado de locais.
Foco Estratégico em Cidades de Alto Desempenho: Considerando a análise de Pareto, estratégias de marketing, expansão de serviços e parcerias podem ser otimizadas concentrando-se nas cidades que contribuem significativamente para as vendas. Isso pode resultar em uma alocação mais eficiente de recursos e um aumento geral na receita.
Segmentação e Personalização: A concentração de vendas em algumas cidades sugere a importância de segmentar e personalizar abordagens de marketing e serviço ao cliente para atender às demandas específicas dessas áreas-chave.
Pareto das Cidades¶
# criando um gráfico de linha do Plotly
fig = px.line(top_ordersbyvalue_cities, x=range(1, len(top_ordersbyvalue_cities)+1), y="Cum % of Total Payments")
# atualizando as configurações de layout do gráfico
fig.update_layout(
title="% de Contribuição das Vendas por Número de Cidades",
xaxis_title="Número de Cidades",
yaxis_title="% de Contribuição para as Vendas"
)
# preenchendo a área abaixo da curva
fig.add_shape(
type="rect",
xref="x",
yref="y",
x0=0,
y0=0,
x1=358,
y1=top_ordersbyvalue_cities["Cum % of Total Payments"][357],
fillcolor="green",
opacity=0.3,
layer="below",
line_width=0
)
# adicionando um texto ao gráfico
fig.add_annotation(
x=1000,
y=70,
text="358 cidades (+-8,7% do total) <br>contribuem para +-80% das vendas.",
font=dict(
size=14,
color="black"
),
showarrow=False
)
fig.show()
print("Número de cidades que contribuem com 80% das vendas totais:",
len(top_ordersbyvalue_cities[top_ordersbyvalue_cities["Cum % of Total Payments"] <= 80]),
"ou em in %:",
(len(top_ordersbyvalue_cities[top_ordersbyvalue_cities["Cum % of Total Payments"] <= 80]) / len(top_ordersbyvalue_cities)) * 100)
Número de cidades que contribuem com 80% das vendas totais: 358 ou em in %: 8.746640605912534
Variação ao longo do tempo¶
Como os pedidos são feitos ao longo do dia? De manhã? De tarde? Na madruga? Às segundas, sábados? É importante saber se estamos vendendo mais ou menos, nos horários e dias das semana!
# Convertendo as colunas de datas para datetime
datesCols = ["order_purchase_timestamp", "order_approved_at", "order_delivered_carrier_date",
"order_delivered_customer_date", "order_estimated_delivery_date", "shipping_limit_date",
"review_creation_date", "review_answer_timestamp"]
for col in datesCols:
data[col] = pd.to_datetime(data[col])
# Ordenando por hora
orders_df["order_purchase_timestamp"] = pd.to_datetime(orders_df["order_purchase_timestamp"])
orderbyhour = orders_df.groupby(orders_df["order_purchase_timestamp"].dt.hour)["order_id"].count().reset_index().sort_values(by="order_purchase_timestamp", ascending=False)
orderbyhour.rename(columns={"order_id":"Total Orders", "order_purchase_timestamp": "Hour of Day"}, inplace=True)
# Gráfico
fig = px.bar(orderbyhour, x='Hour of Day', y='Total Orders', title='Número de pedidos por hora do dia')
fig.update_xaxes(title='Hora do dia')
fig.update_yaxes(title='Número total de pedidos')
fig.show()
Os pedidos começam a aumentar por volta das 6h da manhã e atingem o pico às 4 da tarde. Os pedidom se mantem em uma boa margem até as 22h antes de começarem a cair.
Picos de Atividade: O pico de pedidos às 16h sugere um momento de alta demanda. Pode ser benéfico otimizar recursos, como equipe e logística, para lidar eficientemente com esse aumento de atividade.
Horários Estratégicos para Promoções: Considerando que os pedidos permanecem em uma boa margem até as 22h, este período pode ser estrategicamente explorado para promoções especiais ou ofertas que incentivem compras durante as últimas horas do dia.
Gestão de Estoque: Entender os horários de pico e declínio pode ajudar na gestão eficaz do estoque, garantindo que haja produtos disponíveis durante os momentos de maior demanda e evitando excessos durante os períodos de menor atividade.
Otimização de Entregas e Logística: Com o conhecimento dos horários de pico, a otimização das operações logísticas pode ser implementada para garantir entregas rápidas e eficientes nos momentos em que a demanda é mais alta.
Personalização de Experiência do Cliente: Adaptar estratégias de marketing e atendimento ao cliente para coincidir com os horários de maior atividade pode melhorar a experiência do cliente durante esses períodos críticos.
Análise de Tendências ao Longo do Dia: Observar como os padrões de pedidos variam ao longo do dia pode fornecer insights sobre o comportamento do consumidor e orientar ações específicas para maximizar as oportunidades em diferentes momentos.
# Ordenandopor dia da semana
orderbydow = data.groupby(data["order_purchase_timestamp"].dt.day_name())["order_id"].count().reset_index()
orderbydow.rename(columns={"order_id":"Total Orders", "order_purchase_timestamp": "Weekday Name"}, inplace=True)
orderbydow = orderbydow.sort_values(by="Total Orders", ascending=False)
# Gráfico
fig = px.bar(orderbydow, x='Weekday Name', y='Total Orders', title='Número de pedidos por dia da semana')
fig.update_xaxes(title='Dia da semana')
fig.update_yaxes(title='Número total de pedidos')
fig.show()
# Definindo a ordem dos dias da semana
weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
# Convertendo a coluna "Weekday Name" em uma categoria ordenada
weekday = pd.Categorical(orderbydow['Weekday Name'], categories=weekday_order, ordered=True)
# Criando um novo dataframe ordenado pela coluna de categoria "weekday"
orderbydow_ordered = orderbydow.assign(weekday=weekday).sort_values('weekday')
# Criando um gráfico de barras com os dias da semana em ordem
fig = px.bar(orderbydow_ordered, x='weekday', y='Total Orders', title='Número de pedidos por dia da semana')
fig.update_xaxes(title='Dia da semana')
fig.update_yaxes(title='Número total de pedidos')
fig.show()
Os pedidos atingem o pico no início da semana (segunda e terça-feira) e começam a declinar um pouco depois. Durante o final de semana, observa-se uma diminuição acentuada dos pedidos.
Avaliação dos Produtos¶
De que adianta vender muito, entregar rápido, se as notas dos clientes estiverem baixas? Isso queima o filme de qualquer negócio! Vamos entrar mais a fundo na questão.
# Agrupando - Melhores avaliações
reviewsocres = data.groupby("product_category_name")["review_score"].agg(["mean", "count"]).sort_values(by="mean",ascending=False)
bestrated = reviewsocres[reviewsocres["count"]>=30][:10]
# Gráfico
fig = go.Figure(go.Bar(
x=bestrated['mean'],
y=bestrated.index,
orientation='h'))
fig.update_layout(
title='Produtos com as melhores avaliações',
xaxis_title='Avaliação média',
yaxis_title='Categoria do produto',
height=600,
width=800,
margin=dict(l=100, r=20, t=50, b=50),
)
fig.show()
# Agrupando - piores avaliações
reviewsocres = data.groupby("product_category_name")["review_score"].agg(["mean", "count"]).sort_values(by="mean",ascending=False)
worstrated = reviewsocres[reviewsocres["count"]>=30].sort_values(by='mean')[:10]
# Gráfico
fig = go.Figure(go.Bar(
x=worstrated['mean'],
y=worstrated.index,
orientation='h'))
fig.update_layout(
title='Produtos com as piores avaliações',
xaxis_title='Avaliação média',
yaxis_title='Categoria do produto',
height=600,
width=800,
margin=dict(l=100, r=20, t=50, b=50),
)
fig.show()
Entre as categorias nos dois top 10, os livros gerais e importados obtêm as pontuações mais elevadas, registrando 4,4. Em contrapartida, as fraldas recebem as notas mais baixas, marcando 3,2, seguidas pelos móveis de escritório, que alcançam 3,5.
Previsibilidade e Experiência do Cliente: Livros, sendo produtos gerais e importados, provavelmente recebem pontuações mais altas devido à previsibilidade do conteúdo. Os clientes frequentemente têm uma ideia clara do que esperar, contribuindo para uma experiência mais controlada e, portanto, avaliações mais positivas.
Complexidade e Variedade de Produtos: Produtos como fraldas e móveis de escritório são mais complexos, com uma variedade de características que podem influenciar a satisfação do cliente. A qualidade, o tamanho, a durabilidade e outros fatores podem afetar a experiência, resultando em uma gama mais ampla de opiniões e, consequentemente, pontuações mais variadas.
Expectativas do Cliente: A alta pontuação para livros pode indicar que os clientes geralmente têm expectativas claras ao comprar esses produtos. No entanto, para itens como fraldas e móveis de escritório, as expectativas podem ser mais diversas, levando a uma variação nas avaliações.
Avaliação Subjetiva vs. Objetiva: As pontuações mais baixas para fraldas e móveis de escritório podem refletir avaliações mais subjetivas, influenciadas por preferências individuais e experiências pessoais. Isso destaca a importância de considerar a natureza subjetiva das avaliações ao interpretar os resultados.
Gestão de Expectativas: Os resultados sugerem a importância da gestão eficaz das expectativas do cliente para produtos mais complexos. Comunicar claramente as características e garantir uma experiência positiva desde a compra até o uso pode impactar significativamente nas avaliações.
O método de pagamento afeta o status do pedido?¶
# Por exemplo: o pagamento em dinheiro aumenta o cancelamento de pedidos?
cashvscancel = pd.crosstab(data["payment_type"], data["order_status"])
cashvscancel = cashvscancel[["canceled", "delivered"]]
cashvscancel["% Canceled"] = (cashvscancel["canceled"] / cashvscancel["delivered"] ) * 100
cashvscancel["Avg Cancelation Rate"] = (len(data[data["order_status"] == "canceled"]) / len(data[data["order_status"] == "delivered"])) * 100
cashvscancel
| order_status | canceled | delivered | % Canceled | Avg Cancelation Rate |
|---|---|---|---|---|
| payment_type | ||||
| boleto | 92 | 22029 | 0.417631 | 0.473456 |
| credit_card | 411 | 83536 | 0.492003 | 0.473456 |
| debit_card | 6 | 1623 | 0.369686 | 0.473456 |
| voucher | 27 | 6022 | 0.448356 | 0.473456 |
Podemos ver que a taxa de cancelamento é praticamente a mesma em todos os métodos de pagamento. Mas notamos uma leve desvio acima da média para o cartão de crédito.
Há alguma relação entre o tempo de entrega e as pontuações das avaliações?¶
# Adicionando uma coluna delta que calcula o tempo que levou para o pedido ser entregue
data["TimeToDeliveryinHours"] = (data["order_delivered_customer_date"] - data["order_purchase_timestamp"])
data["TimeToDeliveryinHours"] = data["TimeToDeliveryinHours"].apply(lambda x: x.total_seconds())
data["TimeToDeliveryinHours"] = round((data["TimeToDeliveryinHours"] / 3600) / 24, 2)
data.rename(columns={"TimeToDeliveryinHours" : "TimeToDeliveryinDays"}, inplace=True)
# Principais estatísticas do tempo de entrega
data[["TimeToDeliveryinDays"]].describe()
| TimeToDeliveryinDays | |
|---|---|
| count | 113209.000000 |
| mean | 12.442130 |
| std | 9.356005 |
| min | 0.530000 |
| 25% | 6.740000 |
| 50% | 10.190000 |
| 75% | 15.500000 |
| max | 208.350000 |
O tempo médio de entrega é relativamente alto (12,44 dias), com mediana de (10,19 dias). Observamos um valor extremo atípico de 208 dias.
Vamos dar uma olhada pelo boxplot:
# Definindo a ordem crescente dos valores da pontuação de avaliação
score_order = sorted(data['review_score'].unique())
# Convertendo a coluna "review_score" em uma categoria ordenada
score = pd.Categorical(data['review_score'], categories=score_order, ordered=True)
# Criando um novo dataframe ordenado pela coluna de categoria "score"
data_ordered = data.assign(score=score).sort_values('score')
# Criando um seletor de pontuação de avaliação em ordem crescente
fig = px.box(data_ordered, x='score', y='TimeToDeliveryinDays', color='score',
title='Relação entre a pontuação da avaliação e o tempo de entrega')
fig.update_xaxes(title='Pontuação da avaliação')
fig.update_yaxes(title='Tempo de entrega (dias)')
fig.show()
Podemos ver que temos vários valores atípicos. Estes são pedidos que demoraram muito para serem entregues por algum motivo.
# Definindo o limite superior dos valores
q_high = data["TimeToDeliveryinDays"].quantile(0.95)
# Criando um novo dataframe sem os valores atípicos
data_no_outliers = data[data["TimeToDeliveryinDays"] < q_high]
# Definindo a ordem crescente dos valores da pontuação de avaliação
score_order = sorted(data_no_outliers['review_score'].unique())
# Convertendo a coluna "review_score" em uma categoria ordenada
score = pd.Categorical(data_no_outliers['review_score'], categories=score_order, ordered=True)
# Criando um novo dataframe com a coluna de categoria "score"
data_score = data_no_outliers.assign(score=score)
data_score = data_score.assign(score=score).sort_values('score')
# Criando um seletor de pontuação de avaliação em ordem crescente
fig = px.box(data_score, x='score', y='TimeToDeliveryinDays', color='score',
title='Relação entre a pontuação da avaliação e o tempo de entrega')
fig.update_xaxes(title='Pontuação da avaliação', categoryorder='array', categoryarray=score_order)
fig.update_yaxes(title='Tempo de entrega (dias)')
fig.show()
Nossa hipótese inicial é confirmada. Podemos claramente observar uma relação direta entre o tempo de entrega e a pontuação da avaliação. À medida que o tempo de entrega diminui, a pontuação da avaliação tende a aumentar.
Quais são as cidades dos vendedores com menor/maior tempo de entrega¶
# Vendedores com melhor tempo de entrega
sellersdeliverytime = data.groupby("seller_city")["TimeToDeliveryinDays"].agg(["min", "max", "mean", "std", "count" ]).dropna().sort_values("mean").reset_index()
# Filtro para vendedores com 30 ou mais pedidos em seu histórico
sellersdeliverytime = sellersdeliverytime[sellersdeliverytime["count"]>=30]
fastestdeliverysellers = sellersdeliverytime[:10]
slowestdeliverysellers = sellersdeliverytime.sort_values("mean", ascending=False)[:10]
# Vendedores com entrega mais rápida.
fastestdeliverysellers
| seller_city | min | max | mean | std | count | |
|---|---|---|---|---|---|---|
| 14 | varzea paulista | 2.58 | 21.07 | 5.877297 | 3.284975 | 37 |
| 41 | hortolandia | 1.39 | 71.09 | 7.587543 | 6.676258 | 350 |
| 53 | ribeirao preto / sao paulo | 2.45 | 27.32 | 7.944500 | 4.055726 | 40 |
| 60 | rio claro | 1.70 | 34.88 | 8.144035 | 6.283043 | 114 |
| 61 | ferraz de vasconcelos | 1.17 | 20.84 | 8.186316 | 6.443319 | 38 |
| 63 | vicente de carvalho | 1.54 | 42.43 | 8.309910 | 6.604570 | 221 |
| 67 | poa | 1.09 | 38.20 | 8.533029 | 6.005796 | 175 |
| 70 | carazinho | 3.16 | 19.13 | 8.640656 | 3.858348 | 61 |
| 79 | betim | 1.24 | 53.85 | 8.818312 | 6.242661 | 314 |
| 85 | maua | 1.51 | 42.57 | 8.977107 | 5.455932 | 484 |
# Vendedores com entrega mais lenta.
slowestdeliverysellers
| seller_city | min | max | mean | std | count | |
|---|---|---|---|---|---|---|
| 515 | mombuca | 6.03 | 88.24 | 22.921515 | 17.782976 | 33 |
| 509 | itaquaquecetuba | 1.61 | 194.85 | 21.838281 | 12.911572 | 1734 |
| 507 | congonhal | 4.31 | 60.82 | 21.590463 | 12.736508 | 108 |
| 504 | balneario camboriu | 3.25 | 79.36 | 20.641613 | 17.449918 | 31 |
| 503 | tubarao | 6.01 | 81.33 | 19.930286 | 20.308568 | 35 |
| 499 | foz do iguacu | 2.98 | 92.78 | 19.490833 | 17.240529 | 192 |
| 492 | bombinhas | 5.13 | 68.10 | 18.875761 | 9.758911 | 92 |
| 491 | criciuma | 3.73 | 71.16 | 18.396000 | 15.760557 | 35 |
| 488 | claudio | 5.73 | 48.63 | 18.171318 | 8.158321 | 129 |
| 482 | caucaia | 3.14 | 138.88 | 17.841471 | 23.052995 | 34 |
# Juntando a pontuação média de avaliação à tabela acima.
avg_review_score_seller = data.groupby("seller_city")["review_score"].mean().dropna().sort_values(ascending=False).reset_index()
sellerPerf = sellersdeliverytime.merge(avg_review_score_seller, on="seller_city")
# Gráfico de regressão linear entre a média de tempo de entrega e a média da pontuação de avaliação
fig = px.scatter(sellerPerf, x='mean', y='review_score', trendline='ols',
title='Relação entre a média de tempo de entrega e a média da pontuação de avaliação')
fig.update_xaxes(title='Média de tempo de entrega (dias)')
fig.update_yaxes(title='Média da pontuação de avaliação')
fig.show()
Estados com maior/menor tempo de entrega¶
# Top 10 estados com o maior tempo médio de entrega
highestTTDstates = data.groupby("customer_state")["TimeToDeliveryinDays"].mean().dropna().sort_values(ascending=False).reset_index()
highestTTDstates = highestTTDstates[:10]
# Gráfico de barras com o tempo médio de entrega por estado
fig = px.bar(highestTTDstates, x='TimeToDeliveryinDays', y='customer_state',
orientation='h', title='Top 10 estados com o maior tempo médio de entrega')
fig.update_yaxes(title='Estado')
fig.update_xaxes(title='Tempo médio de entrega (dias)')
fig.show()
# Top 10 estados com o menor tempo médio de entrega
lowestTTDstates = data.groupby("customer_state")["TimeToDeliveryinDays"].mean().dropna().sort_values(ascending=True).reset_index()
lowestTTDstates = lowestTTDstates[:10]
# Gráfico de barras com o tempo médio de entrega por estado
fig = px.bar(lowestTTDstates, x='TimeToDeliveryinDays', y='customer_state',
orientation='h', title='Top 10 estados com o menor tempo médio de entrega')
fig.update_yaxes(title='Estado')
fig.update_xaxes(title='Tempo médio de entrega (dias)')
fig.show()
Infraestrutura Logística: Explorar melhorias na infraestrutura logística nessas regiões pode ser crucial para reduzir os prazos de entrega. Investir em rotas eficientes, centros de distribuição estratégicos e parcerias logísticas pode otimizar o processo.
Demanda e Planejamento: Entender a demanda específica desses estados permite um planejamento mais eficaz. Ajustar os estoques com base nas necessidades regionais pode contribuir para entregas mais rápidas e eficientes.
Parcerias Locais: Estabelecer parcerias sólidas com empresas locais de transporte e distribuição pode ser uma estratégia eficaz. Essas parcerias podem proporcionar uma compreensão mais profunda das condições locais e contribuir para a agilidade nas entregas.
Estratégias de Roteamento Inteligente: Implementar sistemas de roteamento inteligente, utilizando tecnologias como rastreamento em tempo real e análise de dados, pode otimizar a entrega, considerando fatores específicos de cada região.
Comunicação Transparente: Fornecer informações transparentes aos clientes sobre os prazos de entrega estimados pode gerar confiança. A comunicação proativa sobre eventuais atrasos e atualizações em tempo real pode melhorar a experiência do cliente.
Análise Contínua: Realizar análises periódicas dos processos logísticos, identificando gargalos e oportunidades de melhoria, é fundamental. A adaptação constante às condições específicas de cada região contribuirá para uma entrega mais eficiente.
Ao focar nessas áreas, a empresa pode desenvolver estratégias direcionadas para melhorar a eficiência logística, proporcionando uma experiência mais positiva aos clientes desses estados da região Nordeste.
Como o tempo médio de entrega varia ao longo do tempo?¶
# Mediana do tempo de entrega por ano
deliverytimevstime = data.groupby(data["order_purchase_timestamp"].dt.year)["TimeToDeliveryinDays"].median().dropna()
# Convertendo ano em categoria (e não um número)
deliverytimevstime.index = deliverytimevstime.index.astype(str)
# Gráfico de barras com a mediana do tempo de entrega por ano
fig = px.bar(deliverytimevstime, x=deliverytimevstime.index, y='TimeToDeliveryinDays',
title='Mediana do tempo de entrega por ano')
fig.update_xaxes(title='Ano')
fig.update_yaxes(title='Mediana de entrega em dias')
fig.show()
Podemos ver que a empresa fez uma melhoria significativa no tempo de entrega. Em 2016, o tempo médio de entrega excedia 17 dias, enquanto em 2017 ele caiu para cerca de 12 dias e para cerca de 9 dias em 2018.
Como a pontuação média das avaliações varia ao longo do tempo¶
# Média da pontuação da avaliação por ano
scorevstime = data.groupby(data["order_purchase_timestamp"].dt.year)["review_score"].mean().dropna()
# Converte o índice do grupo em uma categoria
scorevstime.index = scorevstime.index.astype(str)
# Gráfico de barras com a média da pontuação da avaliação por ano
fig = px.bar(scorevstime, x=scorevstime.index, y='review_score',
title='Média da pontuação da avaliação por ano')
fig.update_xaxes(title='Ano')
fig.update_yaxes(title='Pontuação média da avaliação')
fig.show()
Os clientes estão dando pontuações melhores em 2017 e 2018 do que em 2016.
Categorias de produtos mais vendidas¶
# Agrupamento
top_categ_by_revenue = data.groupby("product_category_name").agg({'order_id':'nunique','payment_value':'sum'}).sort_values("payment_value", ascending=False)[:10]
top_categ_by_revenue.rename(columns={"order_id":"NumOfOrders", "payment_value":"Revenues"}, inplace=True)
top_categ_by_revenue
| NumOfOrders | Revenues | |
|---|---|---|
| product_category_name | ||
| cama_mesa_banho | 9313 | 1725465.67 |
| beleza_saude | 8770 | 1646292.53 |
| informatica_acessorios | 6649 | 1592611.66 |
| moveis_decoracao | 6398 | 1427214.01 |
| relogios_presentes | 5576 | 1420682.17 |
| esporte_lazer | 7669 | 1390581.87 |
| utilidades_domesticas | 5843 | 1091709.15 |
| ferramentas_jardim | 3496 | 834757.45 |
| automotivo | 3877 | 802254.74 |
| cool_stuff | 3599 | 772616.70 |
Mapa coroplético com o tempo de entrega em dias por cada unidade federativa¶
Para fazer isso, precisamos de um arquivo que tenha as delimitações dos estados brasileiros em forma de polígonos. Podemos encontrar esse arquivo aqui
Para ler o arquivo, utilizaremos o Geopandas! É uma biblioteca Python que estende o poder do Pandas para permitir a manipulação de dados geoespaciais.
# Agrupando os dados por estado e obter a média do tempo de entrega
mean_delivery_time_by_state = data.groupby('customer_state')['TimeToDeliveryinDays'].mean().reset_index()
mean_delivery_time_by_state
| customer_state | TimeToDeliveryinDays | |
|---|---|---|
| 0 | AC | 20.544348 |
| 1 | AL | 24.498128 |
| 2 | AM | 26.382395 |
| 3 | AP | 28.070000 |
| 4 | BA | 19.152422 |
| 5 | CE | 20.646928 |
| 6 | DF | 12.949766 |
| 7 | ES | 15.532738 |
| 8 | GO | 15.249974 |
| 9 | MA | 21.534691 |
| 10 | MG | 11.948940 |
| 11 | MS | 15.493250 |
| 12 | MT | 17.858972 |
| 13 | PA | 23.476657 |
| 14 | PB | 20.425355 |
| 15 | PE | 18.200039 |
| 16 | PI | 19.326229 |
| 17 | PR | 11.963043 |
| 18 | RJ | 15.182924 |
| 19 | RN | 19.686383 |
| 20 | RO | 19.822920 |
| 21 | RR | 28.251591 |
| 22 | RS | 15.160591 |
| 23 | SC | 14.862988 |
| 24 | SE | 21.265183 |
| 25 | SP | 8.720986 |
| 26 | TO | 17.144225 |
import geopandas as gpd
geojson_file = './data/raw/uf.json'
geojson_data = gpd.read_file(geojson_file)
geojson_data.head(10)
| UF_05 | MICRO | MESO | REGIAO | NOME_UF | GEOCODIGO | geometry | |
|---|---|---|---|---|---|---|---|
| 0 | AC | BRASILEIA | VALE DO ACRE | Norte | Acre | 12 | POLYGON ((-73.80098 -7.11146, -73.73763 -7.134... |
| 1 | AL | ALAGOANA DO SERTAO DO SAO FRANCISCO | AGRESTE ALAGOANO | Nordeste | Alagoas | 27 | POLYGON ((-38.23724 -9.32937, -38.20273 -9.296... |
| 2 | AM | ALTO SOLIMOES | CENTRO AMAZONENSE | Norte | Amazonas | 13 | POLYGON ((-73.80098 -7.11146, -73.79617 -7.102... |
| 3 | AP | AMAPA | NORTE DO AMAPA | Norte | Amapá | 16 | POLYGON ((-54.87178 2.43408, -54.79692 2.43935... |
| 4 | BA | ALAGOINHAS | CENTRO NORTE BAIANO | Nordeste | Bahia | 29 | POLYGON ((-46.60921 -11.25550, -46.58164 -11.2... |
| 5 | CE | BAIXO CURU | CENTRO-SUL CEARENSE | Nordeste | Ceará | 23 | POLYGON ((-41.41398 -3.34957, -41.41194 -3.340... |
| 6 | DF | BRASILIA | DISTRITO FEDERAL | Centro-Oeste | DF | 53 | POLYGON ((-48.28216 -15.82915, -48.26746 -15.8... |
| 7 | ES | AFONSO CLAUDIO | CENTRAL ESPIRITO-SANTENSE | Sudeste | Espírito Santo | 32 | MULTIPOLYGON (((-41.87939 -20.75908, -41.87372... |
| 8 | GO | ANAPOLIS | CENTRO GOIANO | Centro-Oeste | Goiás | 52 | POLYGON ((-53.25072 -17.61863, -53.24694 -17.6... |
| 9 | MA | AGLOMERACAO URBANA DE SAO LUIS | CENTRO MARANHENSE | Nordeste | Maranhão | 21 | MULTIPOLYGON (((-48.75472 -5.34878, -48.58427 ... |
# Criando tempo de entrega dentro do geojson
geojson_data['tempo_entrega'] = mean_delivery_time_by_state['TimeToDeliveryinDays']
geojson_data.head()
| UF_05 | MICRO | MESO | REGIAO | NOME_UF | GEOCODIGO | geometry | tempo_entrega | |
|---|---|---|---|---|---|---|---|---|
| 0 | AC | BRASILEIA | VALE DO ACRE | Norte | Acre | 12 | POLYGON ((-73.80098 -7.11146, -73.73763 -7.134... | 20.544348 |
| 1 | AL | ALAGOANA DO SERTAO DO SAO FRANCISCO | AGRESTE ALAGOANO | Nordeste | Alagoas | 27 | POLYGON ((-38.23724 -9.32937, -38.20273 -9.296... | 24.498128 |
| 2 | AM | ALTO SOLIMOES | CENTRO AMAZONENSE | Norte | Amazonas | 13 | POLYGON ((-73.80098 -7.11146, -73.79617 -7.102... | 26.382395 |
| 3 | AP | AMAPA | NORTE DO AMAPA | Norte | Amapá | 16 | POLYGON ((-54.87178 2.43408, -54.79692 2.43935... | 28.070000 |
| 4 | BA | ALAGOINHAS | CENTRO NORTE BAIANO | Nordeste | Bahia | 29 | POLYGON ((-46.60921 -11.25550, -46.58164 -11.2... | 19.152422 |
# Mapa
fig = px.choropleth_mapbox(geojson_data,
geojson=geojson_data.geometry,
locations=geojson_data.index,
color='tempo_entrega',
color_continuous_scale='YlOrRd',
mapbox_style='open-street-map',
zoom=3, center={'lat': -15.788497, 'lon': -47.879873},
opacity=0.5)
fig.show()
Considerações sobre a Análise¶
A análise abrangente dos dados da Olist proporcionou insights valiosos. Destacam-se a importância de cultivar relacionamentos com os principais clientes, a concentração de atividade econômica em algumas cidades estratégicas e a necessidade de gerenciar eficazmente as expectativas do cliente, especialmente em categorias de produtos complexas. Além do cuidado em diversificar as fontes de receita, evitando uma concentração.
A análise logística revelou uma relação direta entre o tempo de entrega e a satisfação do cliente, indicando a necessidade de melhorias na infraestrutura logística e adaptação às demandas regionais. A visualização geoespacial ofereceu uma perspectiva valiosa para ajustar estratégias com base nas condições específicas de cada região.
A evolução positiva ao longo do tempo no tempo de entrega e nas pontuações médias de avaliação reflete a eficácia das iniciativas implementadas. Em resumo, a análise fornece orientações cruciais para estratégias futuras, visando otimização, aprimoramento da experiência do cliente e crescimento sustentável no mercado de comércio eletrônico.